/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.mapping;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 映射的声明语句,在mapper.xml或者接口中的注解中每一个SQL映射语句都对应一个当前对象
 * 主要包含了结果映射、参数映射、是否需要刷新缓存等。
 *
 * @author Clinton Begin
 */
public final class MappedStatement {
    /**
     * Mapper文件或者Mapper接口的文件位置
     */
    private String resource;
    /**
     * Mybatis全局配置
     */
    private Configuration configuration;
    /**
     * CRUD元素配置的全局唯一标志
     */
    private String id;
    /**
     * 控制查询操作中单次查询获取的数据量
     */
    private Integer fetchSize;
    /**
     * 配置Mybatis请求超时时间
     */
    private Integer timeout;
    /**
     * 控制mybatis创建Statement对象的行为
     */
    private StatementType statementType;
    /**
     * 控制mybatis获取ResultSet对象的行为
     */
    private ResultSetType resultSetType;
    /**
     * 维护具体的,由用户配置的CRUD数据
     */
    private SqlSource sqlSource;
    /**
     * 执行CRUD操作时,所使用的缓存对象
     */
    private Cache cache;
    /**
     * 请求入参映射列表
     */
    private ParameterMap parameterMap;
    /**
     * 返回结果映射列表
     */
    private List<ResultMap> resultMaps;
    /**
     * 控制在执行SQL后,是否刷新缓存
     */
    private boolean flushCacheRequired;
    /**
     * 控制在查询时是否使用缓存
     */
    private boolean useCache;
    /**
     * 标志当前查询结果是否是成组且有序的
     */
    private boolean resultOrdered;
    /**
     * 具体执行的SQL操作类型
     */
    private SqlCommandType sqlCommandType;
    /**
     * 主键生成器对象
     */
    private KeyGenerator keyGenerator;
    /**
     * 定义当前语句执行后需要映射出值的指定属性，
     * 配合selectKey子元素或者useGeneratedKeys使用，
     * 可以在语句执行完成之后，
     * 将指定属性对应的数据库值映射到实体中。
     * 如果需要配置多个属性的话，可以使用逗号分隔。
     */
    private String[] keyProperties;
    /**
     * 指定入参对象的主键对应的数据库列名称,当主键列不是表中的第一列时需要配置，
     * 如果需要配置多个属性的话，可以使用逗号分隔。
     */
    private String[] keyColumns;
    /**
     * 是否有嵌套的ResultMap
     */
    private boolean hasNestedResultMaps;
    /**
     * 对应的数据库标志
     */
    private String databaseId;
    /**
     * 对应的log工具实现类
     */
    private Log statementLog;
    /**
     * 语言驱动
     */
    private LanguageDriver lang;

    /**
     * 对于多结果集的名称配置列表
     */
    private String[] resultSets;

    MappedStatement() {
        // constructor disabled
    }

    public static class Builder {

        private MappedStatement mappedStatement = new MappedStatement();

        public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
            // 初始化对应的Mybatis Configuration对象的引用
            mappedStatement.configuration = configuration;
            // 初始化当前声明语句的唯一标志
            mappedStatement.id = id;
            // 初始化对应的Sql内容
            mappedStatement.sqlSource = sqlSource;
            // 初始化JDBC Statement对象的类型
            mappedStatement.statementType = StatementType.PREPARED;
            // 初始化JDBC ResultSet对象的类型
            mappedStatement.resultSetType = ResultSetType.DEFAULT;

            // 初始化一个空的入参映射列表
            mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
            // 初始化一个空的返回参数映射列表
            mappedStatement.resultMaps = new ArrayList<>();
            // 初始化Sql命令的类型
            mappedStatement.sqlCommandType = sqlCommandType;
            // 初始化主键生成器
            mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            String logId = id;
            if (configuration.getLogPrefix() != null) {
                // 使用唯一标志生成日志前缀。
                logId = configuration.getLogPrefix() + id;
            }
            // 初始化对应的日志记录工具
            mappedStatement.statementLog = LogFactory.getLog(logId);
            // 初始化脚本语言解析器
            mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
        }

        public Builder resource(String resource) {
            mappedStatement.resource = resource;
            return this;
        }

        public String id() {
            return mappedStatement.id;
        }

        public Builder parameterMap(ParameterMap parameterMap) {
            mappedStatement.parameterMap = parameterMap;
            return this;
        }

        public Builder resultMaps(List<ResultMap> resultMaps) {
            mappedStatement.resultMaps = resultMaps;
            for (ResultMap resultMap : resultMaps) {
                // 判断是否持有内嵌的ResultMap对象
                mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
            }
            return this;
        }

        public Builder fetchSize(Integer fetchSize) {
            mappedStatement.fetchSize = fetchSize;
            return this;
        }

        public Builder timeout(Integer timeout) {
            mappedStatement.timeout = timeout;
            return this;
        }

        public Builder statementType(StatementType statementType) {
            mappedStatement.statementType = statementType;
            return this;
        }

        public Builder resultSetType(ResultSetType resultSetType) {
            mappedStatement.resultSetType = resultSetType == null ? ResultSetType.DEFAULT : resultSetType;
            return this;
        }

        public Builder cache(Cache cache) {
            mappedStatement.cache = cache;
            return this;
        }

        public Builder flushCacheRequired(boolean flushCacheRequired) {
            mappedStatement.flushCacheRequired = flushCacheRequired;
            return this;
        }

        public Builder useCache(boolean useCache) {
            mappedStatement.useCache = useCache;
            return this;
        }

        public Builder resultOrdered(boolean resultOrdered) {
            mappedStatement.resultOrdered = resultOrdered;
            return this;
        }

        public Builder keyGenerator(KeyGenerator keyGenerator) {
            mappedStatement.keyGenerator = keyGenerator;
            return this;
        }

        public Builder keyProperty(String keyProperty) {
            // 分割出所有的属性名称
            mappedStatement.keyProperties = delimitedStringToArray(keyProperty);
            return this;
        }

        public Builder keyColumn(String keyColumn) {
            // 分割出所有的列名称
            mappedStatement.keyColumns = delimitedStringToArray(keyColumn);
            return this;
        }

        public Builder databaseId(String databaseId) {
            mappedStatement.databaseId = databaseId;
            return this;
        }

        public Builder lang(LanguageDriver driver) {
            mappedStatement.lang = driver;
            return this;
        }

        public Builder resultSets(String resultSet) {
            // 分割出所有的多返回结果集的名称
            mappedStatement.resultSets = delimitedStringToArray(resultSet);
            return this;
        }

        /**
         * @deprecated Use {@link #resultSets}
         */
        @Deprecated
        public Builder resulSets(String resultSet) {
            mappedStatement.resultSets = delimitedStringToArray(resultSet);
            return this;
        }

        public MappedStatement build() {
            assert mappedStatement.configuration != null;
            assert mappedStatement.id != null;
            assert mappedStatement.sqlSource != null;
            assert mappedStatement.lang != null;
            mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
            // 返回MappedStatement对象
            return mappedStatement;
        }
    }

    public KeyGenerator getKeyGenerator() {
        return keyGenerator;
    }

    public SqlCommandType getSqlCommandType() {
        return sqlCommandType;
    }

    public String getResource() {
        return resource;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getId() {
        return id;
    }

    public boolean hasNestedResultMaps() {
        return hasNestedResultMaps;
    }

    public Integer getFetchSize() {
        return fetchSize;
    }

    public Integer getTimeout() {
        return timeout;
    }

    public StatementType getStatementType() {
        return statementType;
    }

    public ResultSetType getResultSetType() {
        return resultSetType;
    }

    public SqlSource getSqlSource() {
        return sqlSource;
    }

    public ParameterMap getParameterMap() {
        return parameterMap;
    }

    public List<ResultMap> getResultMaps() {
        return resultMaps;
    }

    public Cache getCache() {
        return cache;
    }

    public boolean isFlushCacheRequired() {
        return flushCacheRequired;
    }

    public boolean isUseCache() {
        return useCache;
    }

    public boolean isResultOrdered() {
        return resultOrdered;
    }

    public String getDatabaseId() {
        return databaseId;
    }

    public String[] getKeyProperties() {
        return keyProperties;
    }

    public String[] getKeyColumns() {
        return keyColumns;
    }

    public Log getStatementLog() {
        return statementLog;
    }

    public LanguageDriver getLang() {
        return lang;
    }

    public String[] getResultSets() {
        return resultSets;
    }

    /**
     * @deprecated Use {@link #getResultSets()}
     */
    @Deprecated
    public String[] getResulSets() {
        return resultSets;
    }

    /**
     * 根据请求入参获取BoundSql对象
     *
     * @param parameterObject 参数对象
     * @return
     */
    public BoundSql getBoundSql(Object parameterObject) {
        // sqlSource对象的创建工作在解析Mapper Xml 文件时完成
        // 获取对应的BoundSql对象
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

        // 获取Sql对应的入参映射集合
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        if (parameterMappings == null || parameterMappings.isEmpty()) {
            boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }

        // 处理嵌套的参数映射配置
        // check for nested result maps in parameter mappings (issue #30)
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
            // 对应的ResultMap配置唯一标志
            String rmId = pm.getResultMapId();
            if (rmId != null) {
                ResultMap rm = configuration.getResultMap(rmId);
                if (rm != null) {
                    // 重置是否有嵌套返回结果映射标识
                    hasNestedResultMaps |= rm.hasNestedResultMaps();
                }
            }
        }

        return boundSql;
    }

    private static String[] delimitedStringToArray(String in) {
        if (in == null || in.trim().length() == 0) {
            return null;
        } else {
            return in.split(",");
        }
    }

}
